--- title: Edge detection subtitle: Edge detection notebook bigimg: /pages/c_03_edge_detection/c_03_bigimg.jpg share-img: https://raw.githubusercontent.com/YoniChechik/AI_is_Math/master/docs/pages/c_03_edge_detection/c_03_bigimg.jpg layout: notebook ---
# to run in google colab
import sys
if 'google.colab' in sys.modules:
import subprocess
subprocess.call('apt-get install subversion'.split())
subprocess.call('svn checkout https://github.com/YoniChechik/AI_is_Math/trunk/aux_funcs/'.split())
subprocess.call('svn export https://github.com/YoniChechik/AI_is_Math/trunk/c_03_edge_detection/Bikesgray.jpg'.split())
subprocess.call('pip install --upgrade bokeh'.split())
import numpy as np
import cv2
from matplotlib import pyplot as plt
from aux_funcs import *
figsize = (10,10)
img = cv2.imread("Bikesgray.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.figure(figsize=figsize)
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.title('Original image')
img = img.astype(float) # 'uint8' doesn't work with minus sign - for filtering
# 1. cv2.filter2D is working with corelation rether than convolution
# no need to flip the kernel
# 2. Notice that kernel is 2D array - if 1d then column vector
kernel = np.array([[-1, 0, +1]])
dst = cv2.filter2D(img, -1, kernel)
plt.figure(figsize=figsize)
plt.imshow(np.abs(dst), cmap='gray')
plt.colorbar()
plt.title('$f\'_x$: image filtered with '+str(kernel))
Wrong filtering when keeping uint8 instead of float, because uint8 doesn't have negative numbers...
uint8_img = np.zeros((500, 500), dtype=np.uint8)
uint8_img[200:300, 200:300] = 1
kernel = np.array([[-1, 0, +1]])
dst = cv2.filter2D(uint8_img, -1, kernel)
fig, axs = plt.subplots(1, 2, figsize=(20,20))
axs[0].imshow(uint8_img, cmap='gray')
axs[0].title.set_text('original image')
axs[1].imshow(dst, cmap='gray')
axs[1].title.set_text('uint8 WRONG filtering')
kernel = np.array([[-1, 0, +1]]).T
dst = cv2.filter2D(img, -1, kernel)
plt.figure(figsize=figsize)
plt.imshow(dst, cmap='gray')
plt.colorbar()
plt.title('$f\'_y$: image filtered with\n '+str(kernel))
plt.rcParams['figure.figsize'] = [20, 20]
plt.subplot(4, 2, 1)
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.title('original image')
#######################################
kernel = 1/2*np.array([[-1, 0, +1]])
dst_sym = cv2.filter2D(img, -1, kernel)
plt.subplot(4, 2, 2)
plt.imshow(dst_sym, cmap='gray')
plt.title('$f\'_x$: image filtered with symmetric derivative')
#######################################
kernel = 1/6*np.array([
[-1, 0, +1],
[-1, 0, +1],
[-1, 0, +1]])
dst_prewitt = cv2.filter2D(img, -1, kernel)
plt.subplot(4, 2, 3)
plt.imshow(dst_prewitt, cmap='gray')
plt.title('$f\'_x$: image filtered with Prewitt')
#######################################
# cv2.Sobel() also exist
kernel = 1/8*np.array([
[-1, 0, +1],
[-2, 0, +2],
[-1, 0, +1]])
dst_sobel = cv2.filter2D(img, -1, kernel)
plt.subplot(4, 2, 4)
plt.imshow(dst_sobel, cmap='gray')
plt.title('$f\'_x$: image filtered with Sobel')
#######################################
plt.subplot(4, 2, 5)
plt.imshow(np.abs(dst_sobel-dst_sym))
plt.colorbar()
plt.title('|sobel-symmetric|')
#######################################
plt.subplot(4, 2, 6)
plt.imshow(np.abs(dst_sobel-dst_prewitt))
plt.colorbar()
plt.title('|sobel-prewitt|')
#######################################
plt.subplot(4, 2, 7)
plt.imshow(np.abs(dst_sym-dst_prewitt))
plt.colorbar()
plt.title('|symmetric-prewitt|')
kernel = 1/8*np.array([
[-1, 0, +1],
[-2, 0, +2],
[-1, 0, +1]])
sobel_x = cv2.filter2D(img, -1, kernel)
kernel = kernel.T
sobel_y = cv2.filter2D(img, -1, kernel)
mag_img = np.sqrt(sobel_x**2+sobel_y**2)
phase_img = cv2.phase(sobel_x, -sobel_y, angleInDegrees=True)
phase_img_masked = -100*np.ones(phase_img.shape)
TH_PRC = 0.15
th = mag_img.max()*TH_PRC
phase_img_masked = phase_img_masked*(mag_img <= th) + phase_img*(mag_img > th)
bokeh_imshow(mag_img, scale=1, colorbar=True,
show=True, title='Gradient magnitude')
bokeh_imshow(phase_img_masked, scale=1, colorbar=True,
show=True, title='Gradient phase thresholeded')
blur = cv2.blur(img, (1, 1))
kernel = np.array([
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]])
dst_sobel = cv2.filter2D(blur, -1, kernel)
plt.figure(figsize=figsize)
plt.imshow(dst_sobel)
sign_im = dst_sobel > 0
sign_im_left = sign_im[:, :-1]
sign_im_right = sign_im[:, 1:]
res = np.logical_xor(sign_im_left, sign_im_right)
sign_im_left = res[:, 1:]
sign_im_right = res[:, :-1]
res = np.logical_and(sign_im_left, sign_im_right)
plt.figure(figsize=figsize)
plt.imshow(res)
plt.title('Naive way for zero crossings')
It's hard to find zero crossing in an image!! this is why LoG is not in use for edge thinning.
phase_img_q = phase_img.copy()
for i in range(mag_img.shape[0]):
for j in range(mag_img.shape[1]):
phase_img_q[i, j] = np.mod(phase_img_q[i, j]+22.5, 180)
phase_img_q[i, j] = (phase_img_q[i, j])//45 # integer devider
phase_img_q_masked = -1*np.ones(phase_img.shape)
TH_PRC = 0.1
th = mag_img.max()*TH_PRC
phase_img_q_masked = phase_img_q_masked * \
(mag_img <= th) + phase_img_q*(mag_img > th)
bokeh_imshow(phase_img_q_masked, scale=1, colorbar=True, show=True,
title='Gradient phase- quantized and thresholded')
nms = mag_img.copy()
for i in range(1, mag_img.shape[0]-1):
for j in range(1, mag_img.shape[1]-1):
if phase_img_q[i, j] == 0 and (mag_img[i, j+1] > mag_img[i, j] or mag_img[i, j-1] > mag_img[i, j]):
nms[i, j] = -50
if phase_img_q[i, j] == 1 and (mag_img[i+1, j-1] > mag_img[i, j] or mag_img[i-1, j+1] > mag_img[i, j]):
nms[i, j] = -50
if phase_img_q[i, j] == 2 and (mag_img[i-1, j] > mag_img[i, j] or mag_img[i+1, j] > mag_img[i, j]):
nms[i, j] = -50
if phase_img_q[i, j] == 3 and (mag_img[i-1, j-1] > mag_img[i, j] or mag_img[i+1, j+1] > mag_img[i, j]):
nms[i, j] = -50
bokeh_imshow(nms, scale=1, colorbar=True, show=True, title='NMS')
nms_th = np.zeros(nms.shape)
TH_l = 3
TH_h = 13
nms_th[nms >= TH_h] = 2
nms_th[np.bitwise_and(TH_l <= nms, nms < TH_h)] = 1
bokeh_imshow(nms_th, scale=1, colorbar=True, title='double TH')
We will do the iterative process with connected components (CC):
nms_weak_and_strong = np.zeros(nms_th.shape,dtype=np.bool)
nms_strong = np.zeros(nms_th.shape,dtype=np.bool)
nms_weak_and_strong[nms_th>0]=1
nms_strong[nms_th==2]=1
num_w_s_CCs, w_s_CC_mask = cv2.connectedComponents(nms_weak_and_strong.astype(np.uint8))
# for each CC group of weak and strong edge mask
for w_s_CC_i in range(1,num_w_s_CCs):
# get MASK of weak_and_strong edge from index w_s_CC_i
w_s_CC_mask_i = np.zeros(nms_th.shape,dtype=np.bool)
w_s_CC_mask_i[w_s_CC_mask==w_s_CC_i]=1
# if w_s_CC_mask_i has intersection with strong edges mask, add to strong edge mask
if np.any(np.bitwise_and(w_s_CC_mask_i, nms_strong)):
nms_strong = np.bitwise_or(w_s_CC_mask_i, nms_strong)
bokeh_imshow(nms_strong, scale=1, colorbar=True, title='Canny final result')
let's see the results from the default canny of cv2
res = cv2.Canny(img.astype(np.uint8),105,120)
bokeh_imshow(res, scale=1, title='cv2.Canny final result')